/*
 * BlockReaderTests.cpp
 *
 *  Created on: Mar 31, 2010
 *      Author: Lukasz Spintzyk
 */


#include <iostream>
#include <iomanip>
#include "ph.h"
#include "CommandLineParsers.h"
#include "Filesystem.h"
#include "BlockGroup.h"

#include <linux/stat.h>
#include <linux/fs.h>
#include <linux/ext2_fs.h>

#include <cppunit/TestRunner.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/extensions/TestFactoryRegistry.h>


class BlockReaderTests: public CPPUNIT_NS::TestCase
{
    CPPUNIT_TEST_SUITE(BlockReaderTests);
        CPPUNIT_TEST(tryReserveBlocksNearToTest);
        CPPUNIT_TEST(tryReserveBlocksTest);
        CPPUNIT_TEST(reserveBlocksNearToTest);
    CPPUNIT_TEST_SUITE_END();

public:
    void setUp(void)
    {
        tmpnam(d_buffer);
        fclose(fopen(d_buffer, "w"));

        d_opts.blockSize=1024;
        d_opts.blocksPerGroup=245;
        d_opts.inodesPerGroup = 240;
        d_opts.mount.key = "AA::22::33::44::55::66::77::88::01::22::33::44::55::66::77::88";
        d_opts.mount.iv = "1122334455667788A1A2A3A4A5A6A7A8";


        d_opts.mount.file = std::string(d_buffer);
        d_fw = Filesystem::setUp(d_opts);

    }
    void tearDown(void)
    {
    	d_fw.reset();
    	d_fr.reset();
    }

protected:
    void tryReserveBlocksTest()
    {
        BlockManager* r = d_fw->getBlockReader();

       	SuperBlock& sb = d_fw->getSuperBlock();

       	BlockDesc& g = r->getBlockGroupDesc(0);

       	uint32_t inode_blocks_nb = (sb.d_inodesPerGroup*Inode::size())/sb.d_blockSize;




       	for (uint32_t b = 0; b < sb.getInodeTableBlockOffset(); ++b)
       	{
       		CPPUNIT_ASSERT( g.hasBlock(b) );
       	}

       	for (uint32_t b = sb.getInodeTableBlockOffset(); b < inode_blocks_nb; ++b)
       	{
       		CPPUNIT_ASSERT( g.hasBlock(b) );
       	}

       	uint32_t free = sb.d_blocksPerGroup - (sb.getInodeTableBlockOffset()+inode_blocks_nb);
       	for (uint32_t b = sb.getInodeTableBlockOffset()+inode_blocks_nb; b < sb.d_blocksPerGroup; ++b)
		{
			CPPUNIT_ASSERT( !g.hasBlock(b) );
		}


       	uint32_t ret_block=0;
       	for (uint32_t b = 0; b < sb.d_blocksPerGroup; ++b)
       	{
       		uint32_t num = free+1;
       		CPPUNIT_ASSERT( !g.tryReserveBlocks(num, ret_block, b) );
       	}

       	BlockDesc* g0 = 0;
       	for (uint32_t b = 0; b <= sb.getInodeTableBlockOffset()+inode_blocks_nb; ++b)
       	{
       		g0 = &r->getBlockGroupDesc(0);
       		CPPUNIT_ASSERT( g0->tryReserveBlocks(free, ret_block, b) );
       		CPPUNIT_ASSERT( !g0->tryReserveBlocks(free, ret_block, b) );
       	}

       	g0 = &r->getBlockGroupDesc(0);
       	for (uint32_t b = sb.getInodeTableBlockOffset()+inode_blocks_nb+1; b < sb.d_blocksPerGroup; ++b)
       	{
       		CPPUNIT_ASSERT( !g0->tryReserveBlocks(free, ret_block, b) );
       	}


    	g0 = &r->getBlockGroupDesc(0);
    	ret_block = 0 ;
    	for (uint32_t b = 0; b < free; ++b)
		{
    		uint32_t after = 0;
			CPPUNIT_ASSERT( g0->tryReserveBlocks(1, ret_block, after) );
		}


    	g0 = &r->getBlockGroupDesc(0);
    	ret_block = 0 ;
    	for (uint32_t b = 0; b < sb.d_blocksPerGroup; ++b)
		{
    		uint32_t after = ret_block;
    		if (b < free)
    		{
    			CPPUNIT_ASSERT( g0->tryReserveBlocks(1, ret_block, after) );
    		}
    		else
    		{
    			CPPUNIT_ASSERT( !g0->tryReserveBlocks(1, ret_block, after) );
    		}
		}

    	//std::cout << " Reseved" << g0->reservedBlocks()<< std::endl;
    	CPPUNIT_ASSERT(g0->reservedBlocks() == sb.d_blocksPerGroup);
    	CPPUNIT_ASSERT(g0->freeBlocks() == 0);
    	r->setBlockGroupDesc(*g0);


        d_fw.reset();

        FilesystemMountArgs opts2;
        opts2= d_opts.mount;
        d_fr = Filesystem::read(opts2);

        r = d_fr->getBlockReader();
        BlockDesc& gr = r->getBlockGroupDesc(0);

        SuperBlock& sb2 = d_fr->getSuperBlock();
    	CPPUNIT_ASSERT(gr.reservedBlocks() == sb2.d_blocksPerGroup);
    	CPPUNIT_ASSERT(gr.freeBlocks() == 0);
    }
    void tryReserveBlocksNearToTest()
	{
		BlockManager* r = d_fw->getBlockReader();

		SuperBlock& sb = d_fw->getSuperBlock();

		BlockDesc& g = r->getBlockGroupDesc(0);

		uint32_t inode_blocks_nb = (sb.d_inodesPerGroup*Inode::size())/sb.d_blockSize;



		for (uint32_t b = 0; b < sb.getInodeTableBlockOffset(); ++b)
		{
			CPPUNIT_ASSERT( g.hasBlock(b) );
		}

		for (uint32_t b = sb.getInodeTableBlockOffset(); b < inode_blocks_nb; ++b)
		{
			CPPUNIT_ASSERT( g.hasBlock(b) );
		}

		uint32_t free = sb.d_blocksPerGroup - (sb.getInodeTableBlockOffset()+inode_blocks_nb);

		std::vector<uint32_t> blocks;


		CPPUNIT_ASSERT(!r->tryReserveBlocksNearTo(0,free+1));
		CPPUNIT_ASSERT(!r->tryReserveBlocksNearTo(0,free+1));
		CPPUNIT_ASSERT(r->tryReserveBlocksNearTo(0,free));
		CPPUNIT_ASSERT(r->tryReserveBlocksNearTo(0,free));
		CPPUNIT_ASSERT(!r->tryReserveBlocksNearTo(0,free+1));
		CPPUNIT_ASSERT(r->tryReserveBlocksNearTo(0,free));

		d_fw.reset();

		FilesystemMountArgs opts2;
		opts2 = d_opts.mount;
		d_fr = Filesystem::read(opts2);
		r = d_fr->getBlockReader();

       	BlockDesc& g0 = r->getBlockGroupDesc(0);
       	BlockDesc& g1 = r->getBlockGroupDesc(1);

       	for (uint32_t b = 0; b < sb.getInodeTableBlockOffset(); ++b)
       	{
       		CPPUNIT_ASSERT( g0.hasBlock(b) );
       		CPPUNIT_ASSERT( g1.hasBlock(b) );

       	}


	}


    void reserveBlocksNearToTest()
	{
		BlockManager* r = d_fw->getBlockReader();

		SuperBlock& sb = d_fw->getSuperBlock();

		BlockDesc& g = r->getBlockGroupDesc(0);

		uint32_t inode_blocks_nb = sb.d_inodesPerGroup/Inode::size();



		for (uint32_t b = 0; b < sb.getInodeTableBlockOffset(); ++b)
		{
			CPPUNIT_ASSERT( g.hasBlock(b) );
		}

		for (uint32_t b = sb.getInodeTableBlockOffset(); b < inode_blocks_nb; ++b)
		{
			CPPUNIT_ASSERT( g.hasBlock(b) );
		}

		uint32_t free = sb.d_blocksPerGroup - (sb.getInodeTableBlockOffset()+inode_blocks_nb);

		std::vector< std::pair< uint32_t, uint32_t > > blocks;
		CPPUNIT_ASSERT(free+1 == r->reserveBlocksNearTo(0,free+1, blocks));


		//std::cout << "Blocks = " << blocks.size() << std::endl;
		uint32_t sum =0;
		for(std::vector< std::pair< uint32_t, uint32_t > >::const_iterator cit = blocks.begin(); cit != blocks.end(); ++cit)
		{
			sum += (*cit).second;
			//std::cout << " block " << (*cit).first << "  " << (*cit).second << std::endl;
		}
		CPPUNIT_ASSERT(sum == free+1);

		CPPUNIT_ASSERT(!r->tryReserveBlocksNearTo(0,free+1));

		blocks.clear();
		CPPUNIT_ASSERT(15 == r->reserveBlocksNearTo(0, 15, blocks));
		r->writeSuperBlock();
		free = d_fw->getSuperBlock().d_freeBlocks;

		blocks.clear();
		uint32_t newFree = free;
		uint32_t newFree2 = free;
		newFree = r->reserveBlocksNearTo(0, free, blocks);


		CPPUNIT_ASSERT( newFree == newFree2 );

		d_fw.reset();

		d_fr = Filesystem::read(d_opts.mount);
	}


private:


    void setTestBlock(byte* block, int w, int h, int seed=1)
    {
        int k = seed;
        for (int i = 0; i < w; ++i)
        {
            for (int j = 0; j < h; ++j)
            {
                block[i * h + j] = k++;
            }
        }
    }

    void printBlock(byte* block, int w, int h)
    {
        for (int j = 0; j < h; ++j)
        {
            for (int i = 0; i < w; ++i)
            {
                std::cout << std::hex << std::setw(4) << (unsigned int) block[i * h + j]
                        << " ";
            }
            std::cout << std::endl;
        }
    }


    char d_buffer[L_tmpnam];
    char d_bufferBlock[8*1024*10];
    boost::shared_ptr<Filesystem> d_fw;
    boost::shared_ptr<Filesystem> d_fr;
    FilesystemMkfsArgs d_opts;
    static const char* tempTekst;

};

const char* BlockReaderTests::tempTekst = "Ala ma kota kot ma ale";

CPPUNIT_TEST_SUITE_REGISTRATION(BlockReaderTests);

int main(int ac, char **av)
{
    //--- Create the event manager and test controller
    CPPUNIT_NS::TestResult controller;
    //--- Add a listener that colllects test result
    CPPUNIT_NS::TestResultCollector result;
    controller.addListener(&result);
    //--- Add a listener that print dots as test run.
    CPPUNIT_NS::BriefTestProgressListener progress;
    controller.addListener(&progress);
    //--- Add the top suite to the test runner
    CPPUNIT_NS::TestRunner runner;
    runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest());
    runner.run(controller);

    return result.wasSuccessful() ? 0 : 1;
}
